// 雪花算法生成唯一值

interface SnowflakeProps {
  workerId?: number;
  dataCenterId?: number;
  sequence?: number;
}

class Snowflake {
  private epoch: number = 1288834974657;
  private workerIdBits: number = 5;
  private dataCenterIdBits: number = 5;
  private maxWorkerId: number = -1 ^ (-1 << this.workerIdBits);
  private maxDataCenterId: number = -1 ^ (-1 << this.dataCenterIdBits);
  private sequenceBits: number = 12;
  private workerIdShift: number = this.sequenceBits;
  private dataCenterIdShift: number = this.sequenceBits + this.workerIdBits;
  private timestampLeftShift: number = this.sequenceBits + this.workerIdBits + this.dataCenterIdBits;
  private sequenceMask: number = -1 ^ (-1 << this.sequenceBits);
  private lastTimestamp: number = -1;
  private readonly workerId: number = 1;
  private readonly dataCenterId: number = 1;
  private sequence: number = 0;

  constructor(options?: SnowflakeProps) {
    const { workerId = 1, dataCenterId = 1, sequence = 1 } = options || {};
    if (workerId > this.maxWorkerId || workerId < 0) {
      throw new Error(`Worker ID must be greater than 0 and less than maxWorkerId-[${ this.maxWorkerId }]`);
    }
    if (dataCenterId > this.maxDataCenterId || dataCenterId < 0) {
      throw new Error(`Data Center ID must be greater than 0 and less than maxDataCenterId-[${ this.maxDataCenterId }]`);
    }

    this.workerId = workerId;
    this.dataCenterId = dataCenterId;
    this.sequence = sequence;
  }

  private tilNextMillis(lastTimestamp: number): number {
    let timestamp = this.timeGen();
    while (timestamp <= lastTimestamp) {
      timestamp = this.timeGen();
    }
    return timestamp;
  }

  private timeGen(): number {
    return Date.now();
  }

  public nextId(): bigint {
    let timestamp = this.timeGen();
    if (timestamp < this.lastTimestamp) {
      throw new Error(`Clock moved backwards. Refusing to generate id for ${ this.lastTimestamp - timestamp }`);
    }
    if (this.lastTimestamp === timestamp) {
      this.sequence = (this.sequence + 1) & this.sequenceMask;
      if (this.sequence === 0) {
        timestamp = this.tilNextMillis(this.lastTimestamp);
      }
    } else {
      this.sequence = 0;
    }
    this.lastTimestamp = timestamp;
    return ((BigInt(timestamp - this.epoch) << BigInt(this.timestampLeftShift)) |
      (BigInt(this.dataCenterId) << BigInt(this.dataCenterIdShift)) |
      (BigInt(this.workerId) << BigInt(this.workerIdShift)) |
      BigInt(this.sequence));
  }
}

const snowflake = new Snowflake();

const getSnowflakeId = (prefix?: string): string => `${ prefix ? prefix : '' }${ snowflake.nextId() }`;

export {
  SnowflakeProps, Snowflake,
  getSnowflakeId,
};